<template>
  <el-row v-if="easyFlowVisible" class="topo">
    <template v-if="!isPreview">
      <div id="efContainer" ref="efContainer" class="container" v-flowDrag>
        <div class="dotBack">
          <template v-for="node in data.nodeList">

            <el-tooltip class="item" effect="dark" :key="node.id"
                        content="Bottom Center 提示文字" placement="bottom"
                        :disabled="showErrorCompute(node)=='normalNode' ">

              <!-- // 类名 normalNode  正常  、  unknowNode 未知   、  errorNode  有问题的     -->

              <div slot="content">
                {{node.viewData.sysUserName}}<br>{{node.viewData.mobile}}</div>

              <node :id="node.code" :key="node.code" :node="node"
                    :class="showErrorCompute(node)"
                    :activeElement="activeElement"
                    @changeNodeSite="changeNodeSite"
                    @nodeRightMenu="nodeRightMenu" @clickNode="clickNode">
              </node>

            </el-tooltip>

          </template>
        </div>
        <!-- 给画布一个默认的宽度和高度 -->
        <div class="canvasClass">
          &nbsp;
        </div>

      </div>
      <add-node-form v-show="activeElement.type" ref="nodeForm"
                      :ciId="id"  :isAdd="isAdd"
                     @delElement="deleteElement" @setLineLabel="setLineLabel"
                     @repaintEverything="repaintEverything"></add-node-form>
    </template>
    <template v-else>
      <div id="efContainer" ref="efContainer" class="container" v-flowDrag>
        <template v-for="node in data.nodeList">
          <node :id="node.id" :key="node.id" :node="node"
                :activeElement="activeElement" @changeNodeSite="changeNodeSite"
                @nodeRightMenu="nodeRightMenu" @clickNode="clickNode"></node>
        </template>
        <!-- 给画布一个默认的宽度和高度 -->
        <div class="canvasClass">&nbsp;</div>
      </div>
    </template>
  </el-row>
</template>
<script type="text/javascript">
/*
 * @author 宋辉辉
 * @description 拓扑图主页面
 * @api 无
 * @since 2021年11月11日09:39:33
 * @update 2021年11月11日09:39:41
 * @modifier 修改人
 * @bug 无
 * @version v1.0.0
 */
import { jsPlumb } from './jsplumb'
import { easyFlowMixin } from './mixins'
import lodash from 'lodash'
import Node from './node.vue'
import { ForceDirected } from './force-directed'
import { ciInfo as roomCi } from '@/api/assets/ci/room'
import { ciInfo as businessCi } from '@/api/assets/ci/business'

import AddNodeForm from './addNodeForm.vue'
import { getAll } from '@/api/assets/dictionaries/relationship'

import { getAll as getHost } from '@/api/assets/list/host' // 主机

export default {
  components: { Node, AddNodeForm },
  props: { id: Number, isAdd: Boolean, isPreview: Boolean, type: String },
  data() {
    return {
      nodeGroup: [],
      nodePos: [],
      // jsPlumb 实例
      jsPlumb: null,
      // 控制画布销毁
      easyFlowVisible: true,
      // 控制流程数据显示与隐藏
      flowInfoVisible: false,
      // 是否加载完毕标志位
      loadEasyFlowFinish: false,
      flowHelpVisible: false,
      // 数据
      data: {
        lineList: [],
        nodeList: [],
      },
      // 激活的元素、可能是节点、可能是连线
      activeElement: {
        // 可选值 node 、line
        type: undefined,
        // 节点ID
        nodeId: undefined,
        // 连线ID
        sourceId: undefined,
        targetId: undefined,
      },
      typeList: [],
      zoom: 0.5,

      newHostState: [],
      isCreate: false,
    }
  },
  destroyed() {
    console.log('-----------destroyed--------')
  },
  async created() {
    console.log('--------created---------')
    let ret = await getAll()
    this.typeList = ret.data

    this.init()

    //  更新主机装态

    let ret2 = await getHost({
      page: {
        current: 1,
        size: 1000,
      },
    })
    console.log( ret2,'------ 更新主机装态---------');
    if (ret2.code == 20000) {
      let data = ret2.data

      this.newHostState = data.records
      console.log(data.records, '-------新主机状态--------')

      for (let i = 0; i < this.data.nodeList.length; i++) {
        let node = this.data.nodeList[i]
        if (node.nodeType !== 'host') {
          continue
        }

        this.newHostState.forEach((newHost) => {
          if (node.viewData.id == newHost.hostid) {
            node.viewData.available = newHost.available
          }
        })
      }
    }

    // })
  },
  mounted() {
    setTimeout(() => {
      this.$refs.efContainer.scrollTop = 650
      this.$refs.efContainer.scrollLeft = 1000
    }, 400)
  },
  watch: {
    id(val) {
      console.log(val, '-----------------watch id-----')
      this.init()
      setTimeout(() => {
        this.$refs.efContainer.scrollTop = 650
        this.$refs.efContainer.scrollLeft = 1000
      }, 400)
    },
  },
  // 基础配置
  mixins: [easyFlowMixin],
  methods: {
    // 类名 normalNode  正常  、  unknowNode 未知   、  errorNode  有问题的
    showErrorCompute(node) {
      // console.log(this.isCreate, '-------parse-----');
      // console.log(node);

      if (
        node.isNewNode ||
        node.nodeType == 'room' ||
        node.nodeType == 'cabinet' ||
        node.nodeType == 'sys' ||
        node.nodeType == 'line'
      ) {
        return 'normalNode'
      }

      // && node.nodeType == 'host'
      // 0 未知  1  可用  2  不可用
      switch (node.viewData.available) {
        case 0:
          return 'unknowNode'
        case 1:
          return 'normalNode'
        case 2:
          return 'errorNode'
      }

      return 'unknowNode'
    },

    getNodeData() {
      // 将虚拟机，docker ,物理机，数据库 的 nodeType 改为 host
      let data = this.data
      console.log(`data--------------修改前---`, JSON.stringify(data));

      data.nodeList.forEach(el=>{

          if( el.nodeType == 'physical'
            ||el.nodeType == 'virtual'
            ||el.nodeType == 'database'
            ||el.nodeType == 'docker'
          ){
            el.viewData.viewType = el.nodeType
            el.nodeType = 'host'
          }


      })


      console.log(`data----------------修改后---`, JSON.stringify(data));
      return data
    },
    createNew(type) {
      if (this.jsPlumb == null) {
        this.init()
      }
      // 新建视图
      this.isCreate = true

      this.easyFlowVisible = false
      let nodeId = this.getUUID()
      this.data = {
        lineList: [],
        nodeList: [
          {
            nodeType: type == 0 ? 'room' : 'sys',
            nodeName: type == 0 ? '机房' : '业务系统',
            viewData: {
              id: '',
            },
            nodeDataId: '',
            code: nodeId,
            id: null,
          },
        ],
        ciName: '',
        type: type,
      }
      let tempData = lodash.cloneDeep(this.data)
      let listData = ForceDirected(tempData)
      this.dataReload(listData)
      console.log(listData)
      this.jsPlumbInit()
      this.easyFlowVisible = true
    },
    async init() {
      if (this.jsPlumb != null) {
        return
      }
      console.log(this.isAdd, '--init---')
      this.jsPlumb = jsPlumb.getInstance()
      // this.$nextTick(async () => {
      // 默认加载流程A的数据、在这里可以根据具体的业务返回符合流程数据格式的数据即可
      if (!this.isAdd) {
        let res = await roomCi(this.id)
        console.log('------视图中读到的数据--------')
        console.log(JSON.stringify(res))
        this.data = res.data

        // 将数据，封装为想要的样子
        this.data.nodeList.forEach(el=>{
          el.nodeType = el.viewData.viewType
        })

        let tempData = lodash.cloneDeep(this.data)
        let listData = ForceDirected(tempData)
        console.log(listData, '----------- 视图状态  ----')

        this.dataReload(listData)
      }
      // })
    },
    // 返回唯一标识
    getUUID() {
      return Math.random()
        .toString(36)
        .substr(3, 10)
    },
    jsPlumbInit() {
      this.jsPlumb.ready(() => {
        // 导入默认配置
        this.jsPlumb.importDefaults(this.jsplumbSetting)
        // 会使整个jsPlumb立即重绘。
        this.jsPlumb.setSuspendDrawing(false, true)
        // 初始化节点
        this.loadEasyFlow()

        // 单点击了连接线, https://www.cnblogs.com/ysx215/p/7615677.html
        this.jsPlumb.bind('click', (conn, originalEvent) => {
          console.log(conn)

          this.activeElement.type = 'line'
          this.activeElement.sourceId = conn.sourceId
          this.activeElement.targetId = conn.targetId

          this.data.lineList.filter((line) => {
            if (
              line.fromCode == conn.sourceId &&
              line.toCode == conn.targetId
            ) {
              this.$refs.nodeForm.lineInit(line)
            }
          })
        })
        // 连线
        this.jsPlumb.bind('connection', (evt) => {
          let from = evt.source.id
          let to = evt.target.id
          if (this.loadEasyFlowFinish) {
            this.data.lineList.push({ fromCode: from, toCode: to })
          }
        })

        // 删除连线回调
        this.jsPlumb.bind('connectionDetached', (evt) => {
          this.deleteLine(evt.sourceId, evt.targetId)
        })

        // 改变线的连接节点
        this.jsPlumb.bind('connectionMoved', (evt) => {
          this.changeLine(evt.originalSourceId, evt.originalTargetId)
        })

        // 连线右击
        this.jsPlumb.bind('contextmenu', (evt) => {})

        // 连线
        this.jsPlumb.bind('beforeDrop', (evt) => {
          let from = evt.sourceId
          let to = evt.targetId
          if (from === to) {
            this.$msg.warning('节点不支持连接自己')
            return false
          }
          if (this.hasLine(from, to)) {
            this.$msg.warning('该关系已存在,不允许重复创建')
            return false
          }
          if (this.hashOppositeLine(from, to)) {
            this.$msg.warning('不支持两个节点之间连线回环')
            return false
          }
          this.$msg.success('连接成功')
          return true
        })

        // beforeDetach
        this.jsPlumb.bind('beforeDetach', (evt) => {})
        this.jsPlumb.setContainer(this.$refs.efContainer)
      })
    },
    // 加载流程图
    loadEasyFlow() {
      // 初始化节点
      if (this.data.nodeList.length == 0) return
      this.data.nodeList.forEach((node) => {
        if (!this.isPreview) {
          // 设置源点，可以拖出线连接其他节点
          this.jsPlumb.makeSource(
            node.code,
            lodash.merge(this.jsplumbSourceOptions, {})
          )
          // 设置目标点，其他源点拖出的线可以连接该节点
          this.jsPlumb.makeTarget(node.code, this.jsplumbTargetOptions)
        }
        const _this = this
        this.jsPlumb.draggable(node.code, {
          containment: 'parent',
          stop: function(el) {
            // 拖拽节点结束后的对调
          },
        })
      })

      // 初始化连线
      this.data.lineList.forEach((line) => {
        console.log(line, '------初始化连线--')
        let type = this.typeList.filter((item) => {
          return line.typeId == item.id
        })

        var connParam = {
          source: line.fromCode,
          target: line.toCode,
          label: type.length > 0 ? type[0].name : '',
        }

        this.jsPlumb.connect(
          connParam,
          this.jsplumbConnectOptions
        )
      })
      this.$nextTick(function() {
        this.loadEasyFlowFinish = true
      })
    },
    // 设置连线条件
    setLineLabel(from, to, relation) {
      console.log('---------222---')
      var conn = this.jsPlumb.getConnections({
        source: from,
        target: to,
      })[0]
      console.log(conn)

      if (!relation || relation.title === '') {
        conn.removeClass('flowLabel')
        conn.addClass('emptyFlowLabel')
      } else {
        conn.addClass('flowLabel')
      }
      conn.setLabel({
        label: relation.title,
      })
      this.data.lineList.filter((line) => {
        if (line.fromCode == from && line.toCode == to) {
          line.typeId = relation.type
          line.relation = relation
          return line
        }
      })
      this.$emit('change')
    },
    // 删除激活的元素
    deleteElement() {
      if (this.activeElement.type === 'node') {
        this.deleteNode(this.activeElement.nodeId)
      } else if (this.activeElement.type === 'line') {
        this.$confirm('确定删除所点击的线吗?', '提示', {
          confirmButtonText: '确定',
          cancelButtonText: '取消',
          type: 'warning',
        })
          .then(() => {
            var conn = this.jsPlumb.getConnections({
              source: this.activeElement.sourceId,
              target: this.activeElement.targetId,
            })[0]
            this.jsPlumb.deleteConnection(conn)
            this.$emit('change')
          })
          .catch(() => {})
      }
    },
    // 删除线
    deleteLine(from, to) {
      this.data.lineList = this.data.lineList.filter(function(line) {
        if (line.fromCode == from && line.toCode == to) {
          return false
        }
        return true
      })
    },
    // 改变连线
    changeLine(oldFrom, oldTo) {
      this.deleteLine(oldFrom, oldTo)
    },
    // 改变节点的位置
    changeNodeSite(data) {
      for (var i = 0; i < this.data.nodeList.length; i++) {
        let node = this.data.nodeList[i]
        if (node.code === data.nodeId) {
          node.left = data.left
          node.top = data.top
        }
      }
    },
    /**
     * 拖拽结束后添加新的节点
     * @param evt
     * @param nodeMenu 被添加的节点对象
     * @param mousePosition 鼠标拖拽结束的坐标
     */
    addNode(evt, nodeMenu, mousePosition) {
      var screenX = evt.originalEvent.clientX,
        screenY = evt.originalEvent.clientY
      let efContainer = this.$refs.efContainer
      var containerRect = efContainer.getBoundingClientRect()
      var left = screenX,
        top = screenY
      // 计算是否拖入到容器中
      if (
        left < containerRect.x ||
        left > containerRect.width + containerRect.x ||
        top < containerRect.y ||
        containerRect.y > containerRect.y + containerRect.height
      ) {
        this.$message.error('请把节点拖入到画布中')
        return
      }
      left = left - containerRect.x + efContainer.scrollLeft
      top = top - containerRect.y + efContainer.scrollTop
      // 居中
      left -= 85
      top -= 16
      var nodeId = this.getUUID()
      var origName = nodeMenu.name
      var nodeName = origName
      var node = {
        id: null,
        code: nodeId,
        nodeName: nodeName,
        isNewNode: true,
        nodeType: nodeMenu.type,
        nodeDataId: nodeMenu.nodeDataId,
        viewData: {
          id: '',
        },
        left: left + 'px',
        top: top + 'px',
      }
      /**
       * 这里可以进行业务判断、是否能够添加该节点
       */

      this.data.nodeList.push(node)
      this.$nextTick(function() {
        this.jsPlumb.makeSource(nodeId, this.jsplumbSourceOptions)
        this.jsPlumb.makeTarget(nodeId, this.jsplumbTargetOptions)
        this.jsPlumb.draggable(nodeId, {
          containment: 'parent',
          stop: function(el) {
            // 拖拽节点结束后的对调
          },
        })
      })
      this.$emit('change')
    },
    /**
     * 删除节点
     * @param nodeId 被删除节点的ID
     */
    deleteNode(nodeId) {
      this.$confirm('确定要删除节点' + nodeId + '?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning',
        closeOnClickModal: false,
      })
        .then(() => {
          /**
           * 这里需要进行业务判断，是否可以删除
           */
          this.data.nodeList = this.data.nodeList.filter(function(node) {
            if (node.code === nodeId) {
              // 伪删除，将节点隐藏，否则会导致位置错位
              // node.show = false
              return false
            }
            return true
          })
          this.$nextTick(function() {
            this.jsPlumb.removeAllEndpoints(nodeId)
          })
          this.$emit('change')
        })
        .catch(() => {})
      return true
    },
    clickNode(nodeId) {
      console.log(nodeId)
      this.activeElement.type = 'node'
      this.activeElement.nodeId = nodeId
      this.$refs.nodeForm.nodeInit(this.data, nodeId)
    },
    // 是否具有该线
    hasLine(from, to) {
      for (var i = 0; i < this.data.lineList.length; i++) {
        var line = this.data.lineList[i]
        if (line.fromCode === from && line.toCode === to) {
          return true
        }
      }
      return false
    },
    // 是否含有相反的线
    hashOppositeLine(from, to) {
      return this.hasLine(to, from)
    },
    nodeRightMenu(nodeId, evt) {
      this.menu.show = true
      this.menu.curNodeId = nodeId
      this.menu.left = evt.x + 'px'
      this.menu.top = evt.y + 'px'
    },
    repaintEverything() {
      this.$emit('change')
      this.jsPlumb.repaint()
    },
    // 流程数据信息
    dataInfo() {
      this.flowInfoVisible = true
      this.$nextTick(function() {
        this.$refs.flowInfo.init()
      })
    },
    // 加载流程图
    dataReload(data) {
      console.log('加载流程图--------1')
      this.easyFlowVisible = false
      this.data.nodeList = []
      this.data.lineList = []

      this.$nextTick(() => {
        data = lodash.cloneDeep(data)
        this.easyFlowVisible = true
        this.data = data
        console.log(this.data)
        this.$nextTick(() => {
          this.jsPlumb = jsPlumb.getInstance()
          this.$nextTick(() => {
            this.jsPlumbInit()
          })
        })
      })
    },
  },
  directives: {
    flowDrag: {
      bind(el, binding, vnode, oldNode) {
        if (!binding) {
          return
        }
        el.onmousedown = (e) => {
          if (e.button == 2) {
            // 右键不管
            return
          }
          //  鼠标按下，计算当前原始距离可视区的高度
          let disX = e.clientX
          let disY = e.clientY
          el.style.cursor = 'move'

          document.onmousemove = function(e) {
            // 移动时禁止默认事件
            e.preventDefault()
            const left = e.clientX - disX
            disX = e.clientX
            el.scrollLeft += -left

            const top = e.clientY - disY
            disY = e.clientY
            el.scrollTop += -top
          }

          document.onmouseup = function(e) {
            el.style.cursor = 'auto'
            document.onmousemove = null
            document.onmouseup = null
          }
        }
      },
    },
  },
}
</script>

<style scoped>
/* 类名 normalNode  正常  、  unknowNode 未知   、  errorNode  有问题的     */
.errorNode {
  border-color: red;
  border-width: 2px;
  background-color: rgba(255, 0, 0, 0.2);
}

.unknowNode {
  border-color: orange;
  border-width: 2px;
  background-color: rgba(143, 213, 64, 0.1);
}

#efContainer {
  position: relative;
  overflow: scroll;
  width: 100%;
  height: calc(100vh - 121px);
}
.dotBack {
  background-image: url(./image/dot.png);
  background-size: 20px 20px;
  width: 3000px;
  height: 2000px;
}
#efContainer::-webkit-scrollbar {
  width: 0 !important;
}
.topo {
  position: relative;
}
.canvasClass {
  position: absolute;
  top: 2000px;
  left: 3000px;
}
</style>